package com.googlecode.connectlet.crypto.rsa.provider;

import java.security.AlgorithmParameters;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.SecureRandom;
import java.security.interfaces.RSAKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.AlgorithmParameterSpec;

import javax.crypto.Cipher;
import javax.crypto.CipherSpi;

import com.googlecode.connectlet.crypto.rsa.RSA;
import com.googlecode.connectlet.util.Bytes;

public final class RSACipher extends CipherSpi {
	@Deprecated
	@Override
	protected void engineSetMode(String mode) {
		throw new UnsupportedOperationException();
	}

	@Deprecated
	@Override
	protected void engineSetPadding(String padding) {
		throw new UnsupportedOperationException();
	}

	private RSAPublicKey publicKey;
	private RSAPrivateCrtKey privateKey;
	private byte[] block;
	private int blockOffset;

	@Override
	protected void engineInit(int opmode, Key key, SecureRandom random)
			throws InvalidKeyException {
		if (opmode == Cipher.ENCRYPT_MODE) {
			if (key instanceof RSAPublicKey) {
				publicKey = (RSAPublicKey) key;
				privateKey = null;
			} else {
				throw new InvalidKeyException("RSAPublicKey required for ENCRYPT_MODE");
			}
		} else {
			if (key instanceof RSAPrivateCrtKey) {
				publicKey = null;
				privateKey = (RSAPrivateCrtKey) key;
			} else {
				throw new InvalidKeyException("RSAPrivateCrtKey required for DECRYPT_MODE");
			}
		}
		block = new byte[RSA.getBlockSize((RSAKey) key)];
		blockOffset = 0;
	}

	@Override
	protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
			SecureRandom random) throws InvalidKeyException {
		engineInit(opmode, key, random);
	}

	@Override
	protected void engineInit(int opmode, Key key, AlgorithmParameters params,
			SecureRandom random) throws InvalidKeyException {
		engineInit(opmode, key, random);
	}

	@Override
	protected byte[] engineGetIV() {
		return null;
	}

	@Override
	protected AlgorithmParameters engineGetParameters() {
		return null;
	}

	@Override
	protected int engineGetBlockSize() {
		return block.length;
	}

	@Override
	protected int engineGetOutputSize(int inputLen) {
		return block.length;
	}

	@Override
	protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
		System.arraycopy(input, inputOffset, block, blockOffset, inputLen);
		blockOffset += inputLen;
		return new byte[0];
	}

	@Override
	protected int engineUpdate(byte[] input, int inputOffset, int inputLen,
			byte[] output, int outputOffset) {
		System.arraycopy(input, inputOffset, block, blockOffset, inputLen);
		blockOffset += inputLen;
		return 0;
	}

	@Override
	protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) {
		engineUpdate(input, inputOffset, inputLen);
		if (publicKey == null) {
			return RSA.decrypt(privateKey, Bytes.left(block, blockOffset));
		}
		return RSA.encrypt(publicKey, Bytes.left(block, blockOffset));
	}

	@Override
	protected int engineDoFinal(byte[] input, int inputOffset, int inputLen,
			byte[] output, int outputOffset) {
		byte[] result = engineDoFinal(input, inputOffset, inputLen);
		System.arraycopy(result, 0, output, outputOffset, result.length);
		return result.length;
	}
}